;*****************************************************************************************************
;+
; NAME:
;       IGL_IMAGE
;
; PURPOSE:
;
;       This is a basic vizualisation object which represents an
;       image (either true-color or indexed color). It is subclassed
;       from SCALEIMAGE (Catalyst Library) since it was an already
;       nice implementation of an image object.
;
;
; AUTHORS:
;
;        RAPHAEL SADOUN
;        Graduate Student
;        Institut d'Astrophysique de Paris
;        98 bis boulevard Arago
;        75014 PARIS FRANCE
;        E-mail: raphael.sadoun@gmail.com
;
; SUPERCLASSES:
;
;       SCALEIMAGE (Catalyst Library)
;
; SYNTAX:
;
;       image = Obj_New('IGL_Image')
;
;
; CLASS_STRUCTURE:
;
;       class = {IGL_Image, $      
;           INHERITS SCALEIMAGE, $   ; This is a SCALEIMAGE object
;           init_xr:fltarr(2), $     ; The mapping between data and image pixel X coordinates           
;           init_yr:fltarr(2) $      ; The mapping between data and image pixel Y coordinates
;          }
;
;
; MODIFICATION_HISTORY:
;
;       Written by: Raphael Sadoun, 24 April 2012
;-
;*****************************************************************************************************
;+
; NAME:
;       IGL_Image::CONTROLPANEL
;
; PURPOSE:
;
;       This method creates a control panel for the graphic.
;
; SYNTAX:
;
;       self -> ControlPanel, baseObject
;
; ARGUMENTS:
;
;       baseObject:    The object reference of a base widget for this control to
;                      be added to. If not supplied, the control panel will be in a
;                      self contained window.
;
; KEYWORDS:
;
;       _EXTRA:       Any keywords appropriate for the CATCONTROLPANEL::INIT method.
;-
;*****************************************************************************************************
PRO IGL_Image::ControlPanel, baseObject, _EXTRA=extraKeywords

   @cat_pro_error_handler

   ;; Create a new control panel
   cp = OBJ_NEW ('CatControlPanel', self, PARENT=baseObject, COLUMN=1, $
      TITLE='IGL_Image Control Panel', _EXTRA=extraKeywords, /No_Cancel, /No_Apply, /No_OK)

   IF (NOT OBJ_VALID (cp)) THEN RETURN

   aproperties = Obj_New('PROPERTYSHEETWIDGET', cp, Value=self, Name='IGL_IMAGE PROPERTYSHEET', YSize=12)
   aproperties -> SetProperty, Event_Object=self

   ;; Display the control panel if it created its own TLB.
   IF cp -> Created_Own_TLB(tlb) THEN tlb -> Draw, /Center

   self -> Report, /Completed

END

;*****************************************************************************************************
;+
; NAME:
;       IGL_Image::CREATEDISPLAYIMAGE
;
; PURPOSE:
;
;       This method creates a display image for the object.
;
; SYNTAX:
;
;       self -> CreateDisplayImage
;
; ARGUMENTS:
;
;       None.
;
; KEYWORDS:
;
;       None.
;
;-
;*****************************************************************************************************
PRO IGL_Image::CreateDisplayImage

   @cat_pro_error_handler

   ;; The following code is a slight modification to the
   ;; SCALEIMAGE::CreateDisplayImage method. It changes the way the
   ;; zoom rectangle is calculated (based on the current data ranges
   ;; of the coordinate system associated with the image).

   ;; Get the position from the coordinate system if possible and
   ;; assign it to the image
   IF OBJ_VALID(self._coords) THEN self._coords -> GetProperty, Position=pos ELSE pos = self._position
   currentPos = self._position
   self._position = pos
    
   ;; Keep the aspect ratio of the image? If so, maybe change image position.
   position = self -> CheckKeepAspectRatio()   

   ;; Calculate the image size and start locations.
   xsize = Ceil((position[2] - position[0]) * !D.X_VSIZE)
   ysize = Ceil((position[3] - position[1]) * !D.Y_VSIZE)
   xstart = Round(position[0] * !D.X_VSIZE)
   ystart = Round(position[1] * !D.Y_VSIZE)

   ;; Update the location variables, as these may have changed.
   self._location[*,0] = [xstart, ystart, xstart + xsize, ystart + ysize, $
                          Double(!D.X_VSize), Double(!D.Y_VSize)]
   self._location[*,1] = [ self._location[0,0]/self._location[4,0], $
                           self._location[1,0]/self._location[5,0], $
                           self._location[2,0]/self._location[4,0], $
                           self._location[3,0]/self._location[5,0], $
                           self._location[4,0]/self._location[4,0], $
                           self._location[5,0]/self._location[5,0] ]

  ;; Is there a contained coordinate object that needs updating?
  IF Obj_Valid(self._coords) THEN BEGIN
     self._coords -> SetProperty, Position=self._location[0:3,1]
  END
  IF Obj_Valid(self._zoomCoords) THEN BEGIN
     self._zoomCoords -> SetProperty, Position=self._location[0:3,1]
  ENDIF

  ;; The image should be the child of a IGL_Graphic object so we need
  ;; to update the position of the graphic so that it is propagated to
  ;; all the other objects contained in the graphic
  self -> GetProperty, FIRST_PARENT=parent
  IF OBJ_ISA_VALID(parent,'IGL_Graphic') THEN parent -> SetProperty, Position=self._location[0:3,1]

  ;; Calculate the zoom rectangle based on the current data range of
  ;; the coordinate system
  self._coords -> GetProperty, Xrange=xr, Yrange=yr
  IF self.init_xr[0] EQ self.init_xr[1] THEN init_xr = [0,(self.XSize-1) > 1] ELSE init_xr = self.init_xr
  IF self.init_yr[0] EQ self.init_yr[1] THEN init_yr = [0,(self.YSize-1) > 1] ELSE init_yr = self.init_yr
  xims = (self._xsize-1)/(init_xr[1]-init_xr[0]) * (xr-init_xr[0])
  yims = (self._ysize-1)/(init_yr[1]-init_yr[0]) * (yr-init_yr[0])
  self._x1 = Round(xims[0])
  self._x2 = Round(xims[1])
  self._y1 = Round(yims[0])
  self._y2 = Round(yims[1])
  
  ;; Compute a display image.
  IF Ptr_Valid(self._dataPtr) THEN BEGIN

     ;; If this is PostScript, then get the image and RETURN.
     IF (!D.Flags AND 1) NE 0 THEN BEGIN

        ;; Get the image itself.
        CASE self._interleaving OF
            0: image = (*self._dataPtr)[self._x1:self._x2, self._y1:self._y2]
            1: image = (*self._dataPtr)[*, self._x1:self._x2, self._y1:self._y2]
            2: image = (*self._dataPtr)[self._x1:self._x2, *, self._y1:self._y2]
            3: image = (*self._dataPtr)[self._x1:self._x2, self._y1:self._y2, *]
        ENDCASE

        ;; Handle missing value, if you have one.
        IF Ptr_Valid(self.missing_value) THEN BEGIN
           IF Finite(*self.missing_value) THEN BEGIN
               i = Where(image EQ *self.missing_value, count)
           ENDIF ELSE BEGIN
               i = Where(Finite(image) EQ 0, count)
           ENDELSE
           IF count NE 0 THEN BEGIN
               image = Float(Temporary(image))
               image[i] = !VALUES.F_NAN
               self -> GetProperty, COLOR_OBJECT=colors
               colors -> LoadColor, self.missing_color, self.missing_index
           ENDIF
        ENDIF
        i = Where(Finite(image) EQ 0, count)
        image = Temporary(self->ScaleTheImage(image)) + self.bottom
        IF count GT 0 THEN image[i] = self.missing_index
        IF Ptr_Valid(self._displayImage) THEN BEGIN
           *self._displayImage = image
        ENDIF ELSE BEGIN
           self._displayImage = Ptr_New(image)
        ENDELSE
        RETURN
     ENDIF

     ;; It is not PostScript, so find the right image based on interleaving.
     CASE self._interleaving OF

         0: BEGIN

            image = (*self._dataPtr)[self._x1:self._x2, self._y1:self._y2]
            
            ;; Handle missing value, if present.
            IF Ptr_Valid(self.missing_value) THEN BEGIN
                IF Finite(*self.missing_value) THEN BEGIN
                   i = Where(image EQ *self.missing_value, count)
                ENDIF ELSE BEGIN
                   i = Where(Finite(image) EQ 0, count)
                ENDELSE
            ENDIF ELSE count = 0
            IF count NE 0 THEN BEGIN
               image = Float(Temporary(image))
               image[i] = !VALUES.F_NAN
               self -> GetProperty, COLOR_OBJECT=colors
               colors -> LoadColor, self.missing_color, self.missing_index
            ENDIF
            i = Where(Finite(image) EQ 0, count)
            
            ;; Scale the image, create display image.
            image = Temporary(self->ScaleTheImage(image)) + self.bottom
            IF count GT 0 THEN IF Ptr_Valid(self.missing_value) THEN image[i] = self.missing_index
            IF Ptr_Valid(self._displayImage) THEN BEGIN
               *self._displayImage = Congrid(image, xsize, ysize, $
                  INTERP=self._interpolate)
            ENDIF ELSE BEGIN
               self._displayImage = Ptr_New(Congrid(image, xsize, ysize, $
                  INTERP=self._interpolate), /No_Copy)
            ENDELSE

            END

        1: BEGIN
        
            image = (*self._dataPtr)[*, self._x1:self._x2, self._y1:self._y2]

            ;; Handle missing value, if present.
            IF Ptr_Valid(self.missing_value) THEN BEGIN
               i = Where(image EQ *self.missing_value, count)
            ENDIF ELSE BEGIN
               i = Where(Finite(image) EQ 0, count)
            ENDELSE
            IF count NE 0 THEN BEGIN
               image = Float(Temporary(image))
               image[i] = !VALUES.F_NAN
               self -> GetProperty, COLOR_OBJECT=colors
               colors -> LoadColor, self.missing_color, self.missing_index
            ENDIF
            i = Where(Finite(image) EQ 0, count)

            ;; Scale the image, create display image.
            image = Temporary(self->ScaleTheImage(image)) + self.bottom
            IF count GT 0 THEN image[i] = self.missing_index
            IF Ptr_Valid(self._displayImage) THEN BEGIN
              *self._displayImage = Congrid(image, 3, xsize, ysize, $
                 INTERP=self._interpolate)
            ENDIF ELSE BEGIN
              self._displayImage = Ptr_New(Congrid(image, 3, xsize, ysize, $
                 INTERP=self._interpolate), /No_Copy)
            ENDELSE

           END

        2: BEGIN

           image = (*self._dataPtr)[self._x1:self._x2, *, self._y1:self._y2]

            ;; Handle missing value, if present.
            IF Ptr_Valid(self.missing_value) THEN BEGIN
               i = Where(image EQ *self.missing_value, count)
            ENDIF ELSE BEGIN
               i = Where(Finite(image) EQ 0, count)
            ENDELSE
            IF count NE 0 THEN BEGIN
               image = Float(Temporary(image))
               image[i] = !VALUES.F_NAN
               self -> GetProperty, COLOR_OBJECT=colors
               colors -> LoadColor, self.missing_color, self.missing_index
            ENDIF
            i = Where(Finite(image) EQ 0, count)
            image = Temporary(self->ScaleTheImage(image)) + self.bottom
            IF count GT 0 THEN image[i] = self.missing_index
            IF Ptr_Valid(self._displayImage) THEN BEGIN
              *self._displayImage = Congrid(image, ROUND(xsize), 3, ROUND(ysize), $
                 INTERP=self._interpolate)
            ENDIF ELSE BEGIN
              self._displayImage = Ptr_New(Congrid(image, ROUND(xsize), 3, ROUND(ysize), $
                 INTERP=self._interpolate), /No_Copy)
           ENDELSE

           END

        3: BEGIN

           image = (*self._dataPtr)[self._x1:self._x2, self._y1:self._y2, *]

           ;; Handle missing value, if present.
           IF Ptr_Valid(self.missing_value) THEN BEGIN
               i = Where(image EQ *self.missing_value, count)
            ENDIF ELSE BEGIN
               i = Where(Finite(image) EQ 0, count)
            ENDELSE
            IF count NE 0 THEN BEGIN
               image = Float(Temporary(image))
               image[i] = !VALUES.F_NAN
            ENDIF
            i = Where(Finite(image) EQ 0, count)
            image = Temporary(self->ScaleTheImage(image)) + self.bottom
            IF count GT 0 THEN image[i] = self.missing_index
            IF Ptr_Valid(self._displayImage) THEN BEGIN
              *self._displayImage = Congrid(image, xsize, ysize, 3, $
                  INTERP=self._interpolate)
            ENDIF ELSE BEGIN
              self._displayImage = Ptr_New(Congrid(image, xsize, ysize, 3, $
                 INTERP=self._interpolate), /No_Copy)
            ENDELSE

            END

     ENDCASE
  ENDIF

END


;*****************************************************************************************************
;+
; NAME:
;        IGL_Image::EVENT_HANDLER
;
; PURPOSE:
;
;        This method is the event handler for the IGL_Image object. It will typically
;        be used to respond to events from widget objects created in the CONTROLPANEL
;        method.
;
; SYNTAX:
;
;        This method is called automatically by the event handling mechanism.
;
; ARGUMENTS:
;
;       event: The event structure as described in the IDL help files, except
;              that the ID, TOP and HANDLER tags will be object references.
;
; KEYWORDS:
;
;       None.
;
;-
;*****************************************************************************************************
PRO IGL_Image::EventHandler, event

   @cat_pro_error_handler

   ;; Get the name of the widget generating the event. Branch on this.
   event.ID -> GetProperty, Name=eventName
   CASE eventName OF

      'IGL_IMAGE PROPERTYSHEET': BEGIN

         IF event.type EQ 0 THEN BEGIN
            
            CASE StrUpCase(event.identifier) OF

               'COLOR': BEGIN

                  event.component -> GetProperty, Color=color
                  event.id -> GetProperty, ID=group_leader
                  color = PickColorName(color, Group_Leader=group_leader)
                  event.component -> SetProperty, Color=color

                  ;; Refresh the graphics hierarchy
                  IGL_RefreshDraw, event.component, /Buffered_Output
                  
               END

               'POSITION': BEGIN
                  event.component -> GetProperty, Position=pos
                  event.id -> GetProperty, ID=group_leader
                  position = AdjustPosition(pos, Group_Leader=group_leader)
                  event.component -> SetProperty, Position=position
                  
                  ;; Refresh the graphics hierarchy
                  IGL_RefreshDraw, event.component, /Buffered_Output
                  
               END
               
               'SCALETYPE': BEGIN
                  event.component -> GetProperty, SCALETYPE=scaletype
                  identifier = event.identifier
                  event.id -> GetProperty, Value=value, Component=event.component, Property_Value=identifier
                  event.component -> SetPropertyByIdentifier, identifier, value
                  
                  ;; After scale type changes, you need to update the min and max.
                  event.component -> GetProperty, SCLMIN=sclmin, SCLMAX=sclmax
                  event.component -> SetPropertyByIdentifier, 'SCLMIN', sclmin
                  event.component -> SetPropertyByIdentifier, 'SCLMAX', sclmax
                  self._controlPanel -> Refresh_Properties

                  ;; Refresh the graphics hierarchy
                  IGL_RefreshDraw, event.component, /Buffered_Output
                  
               END

               ELSE: BEGIN

                  component = event.component
                  identifier = event.identifier
                  event.id -> GetProperty, Value=value, Component=component, Property_Value=identifier
                  event.component -> SetPropertyByIdentifier, identifier, value

                  IF OBJ_VALID(event.component) THEN IGL_RefreshDraw, event.component, /Buffered_Output

               END

            ENDCASE
         ENDIF

      END

      ELSE: self -> SCALEIMAGE::EventHandler, Event
      
   ENDCASE

   ; Report completion. Object may have been deleted.
   IF Obj_Valid(self) THEN self -> Report, /Completed
   
END


;*****************************************************************************************************
;+
; NAME:
;       IGL_Image::GETPROPERTY
;
; PURPOSE:
;
;       This method is used to get the object's properties.
;
; SYNTAX:
;
;       self -> GetProperty
;
; ARGUMENTS:
;
;     None.
;
; KEYWORDS:
;
;     INITIAL_XRANGE:      The mapping between the data range and the
;                          image pixel X coordinates.
;
;     INITIAL_YRANGE:      The mapping between the data range and the
;                          image pixel Y coordinates.
;
;     _REF_EXTRA:          Any keywords appropriate for the superclass GetProperty method.
;-
;*****************************************************************************************************
PRO IGL_Image::GetProperty, DELETE=delete, $
                            INITIAL_XRANGE=initial_xrange, $
                            INITIAL_YRANGE=initial_yrange, $
                            _REF_EXTRA=extraKeywords

   @cat_pro_error_handler

   IF (arg_present(delete)) THEN delete = 0
   IF (arg_present(initial_xrange)) THEN initial_xrange = self.init_xr
   IF (arg_present(initial_yrange)) THEN initial_yrange = self.init_yr

   IF (N_ELEMENTS(extraKeywords) GT 0) THEN self -> SCALEIMAGE::GetProperty, _EXTRA=extraKeywords

   IF (obj_valid(self)) THEN self -> Report, /Completed

END


;*****************************************************************************************************
;+
; NAME:
;       IGL_Image::PAN
;
; PURPOSE:
;
;       This method pans the image.
;
; SYNTAX:
;
;       self -> Pan, movex, movey
;
; ARGUMENTS:
;
;       movex:  The number of (window) pixels to move in the X direction.
;
;       movey:  The number of (window) pixels to move in the Y direction.
;
; KEYWORDS:
;
;       PIXMAP: Set this keyword to a pixmap that can be used to erase the previous
;               contents of the window.
;
;       NODRAW: If this keyword is set, only the coordinates are updated. No drawing occurs.
;-
;*****************************************************************************************************
PRO IGL_Image::Pan, movex, movey, PIXMAP=pixmap, NODRAW=nodraw

   @cat_pro_error_handler
   
   ;; The following code is a slight modification of the CATIMAGE::Pan
   ;; method. It calculates the new data range and updates the
   ;; coordinate system.

   ;; If the data range is static then return immediately
   IF OBJ_ISA_VALID(self._coords,'IGL_Coord') THEN BEGIN
      self._coords -> GetProperty, STATIC=IsStatic
      IF IsStatic THEN RETURN
   ENDIF

   ;; Get the current zoom rectangle.
   self -> GetProperty, ZoomRect=rect

   movex = -3 > movex < 3
   movey = -3 > movey < 3

   ;; Calculate a factor for moving the image in the image pixel coordinate system
   ;; as opposed to the window coordinate system.
   xfactor = self._xsize / Float(self._location[2,0] - self._location[0,0])
   yfactor = self._ysize / Float(self._location[3,0] - self._location[1,0])

   ;; Move the zoom rectangle by the specified amount in the image pixel coordinate system.
   x1 = Round(rect[0] - (movex * xfactor))
   IF self._order THEN y1 = Round(rect[1] + (movey * yfactor )) ELSE y1 = Round(rect[1] - (movey * yfactor ))
   x2 = Round(rect[2] - (movex * xfactor))
   IF self._order THEN y2 = Round(rect[3] + (movey * yfactor)) ELSE y2 = Round(rect[3] - (movey * yfactor))

   ;; Make sure the endpoints are inside the image.
   IF x1 LT 0 THEN BEGIN
      x2 = x2 + Abs(x1)
      x1 = 0
   ENDIF
   IF y1 LT 0 THEN BEGIN
      y2 = y2 + Abs(y1)
      y1 = 0
   ENDIF
   IF x2 GE (self._xsize-1) THEN BEGIN
      x2 = self._xsize - 1
      x1 = x2 - (rect[2] - rect[0])
   ENDIF
   IF y2 GE (self._ysize-1) THEN BEGIN
      y2 = self._ysize - 1
      y1 = y2 - (rect[3] - rect[1])
   ENDIF

   ;; Update the coordinate system.
   xvec = Scale_Vector(Findgen(self._xsize), self.init_xr[0], self.init_xr[1])
   xr = [xvec[Round(x1)], xvec[Round(x2)]]
   
   yvec = Scale_Vector(Findgen(self._ysize), self.init_yr[0], self.init_yr[1])
   yr = [yvec[Round(y1)], yvec[Round(y2)]]
   
   self -> SetProperty, ZoomRect=Round([x1, y1, x2, y2])
   self._coords -> SetProperty, XRange=xr, YRange=yr
   
   ;; Draw it.
   IF ~Keyword_Set(nodraw) THEN self -> Draw

   ;; Report completion
   self -> Report, /Completed

END


;*****************************************************************************************************
;+
; NAME:
;       IGL_Image::SETPROPERTY
;
; PURPOSE:
;
;       This method is used to set the object's properties.
;
; SYNTAX:
;
;       self -> SetProperty
;
; ARGUMENTS:
;
;     None.
;
; KEYWORDS:
;
;     INITIAL_XRANGE:      The mapping between the data range and the
;                          image pixel X coordinates.
;
;     INITIAL_YRANGE:      The mapping between the data range and the
;                          image pixel Y coordinates.
;
;     DELETE:              Set this keyword to destroy the object.
;
;     _EXTRA:              Any keyword accepted by the SetProperty method of
;                          the SCALEIMAGE superclass.
;-
;*****************************************************************************************************
PRO IGL_Image::SetProperty, INITIAL_XRANGE=initial_xrange, $
                            INITIAL_YRANGE=initial_yrange, $
                            DELETE=delete, $
                            _EXTRA=extraKeywords

   @cat_pro_error_handler

   IF (n_elements(initial_xrange) NE 0) THEN self.init_xr = initial_xrange
   IF (n_elements(initial_yrange) NE 0) THEN self.init_yr = initial_yrange

   IF (N_ELEMENTS(extraKeywords) GT 0) THEN self -> SCALEIMAGE::SetProperty, _EXTRA=extraKeywords

   IF (keyword_set(delete)) THEN BEGIN
      self -> SetProperty, Visible=0
      IGL_RefreshDraw, self, /Buffered_Output
      Obj_Destroy, self._controlPanel
      self -> GetProperty, Parent=parents
      self -> SendMessage, 'OBJECT_DELETED', DATA=self
      FOR j=0,N_Elements(parents)-1 DO IF OBJ_VALID(parents[j]) THEN parents[j] -> Remove, self
      Obj_Destroy, self
      RETURN
   ENDIF

   IF (obj_valid(self)) THEN self -> Report, /Completed

END


;*****************************************************************************************************
;+
; NAME:
;       IGL_Image::ZOOMIN
;
; PURPOSE:
;
;       This method allows the user to zoom into the image.
;
; SYNTAX:
;
;       self -> ZoomIn, x_img, y_img
;
; ARGUMENTS:
;
;       x_img: The X focal point for zooming. If not provided, the center of the image.
;              The value will be a pixel value in the native image coordinate system.
;              That is to say, a value between 0 and the image X size. The value can
;              be obtained from an event structure by passing the event structure to
;              the method Pixel_To_Value:
;
;                 imgVal = self -> Pixel_To_Value(event.x, event.y, XPixel=x_img, YPixel=y_img)
;                 self -> ZoomIn, x_img, y_img
;
;       y_img: The Y focal point for zooming. If not provided, the center of the image.
;              The value will be a pixel value in the native image coordinate system.
;              That is to say, a value between 0 and the image Y size.
;
; KEYWORDS:
;
;       PIXMAP: Set this keyword to a pixmap that can be used to erase the previous
;               contents of the window.
;
;       NODRAW: If this keyword is set, only the coordinates are updated. No drawing occurs.
;
;-
;*****************************************************************************************************
PRO IGL_Image::ZoomIn, x_img, y_img, PIXMAP=pixmap, NODRAW=nodraw 

  @cat_pro_error_handler

  ;; The following code is a slight modification of the CATIMAGE::ZoomIn
  ;; method. It calculates the new data range and updates the
  ;; coordinate system.

  ;; If the data range is static then return immediately
  IF OBJ_ISA_VALID(self._coords,'IGL_Coord') THEN BEGIN
     self._coords -> GetProperty, STATIC=IsStatic
     IF IsStatic THEN RETURN
  ENDIF
  
  ;; Do we have a focal point?
  IF N_Elements(x_img) EQ 0 THEN BEGIN
     x_img = (self._xsize) / 2
     y_img = (self._ysize) / 2
  ENDIF  

  ;; Find the endpoints of a zoom.
  xnumpix = abs(self._x2-self._x1) / 2^0.25
  ynumpix = abs(self._y2-self._y1) / 2^0.25
  x1 = x_img - (xnumpix/2.)
  x2 = x1 + xnumpix
  y1 = y_img - (ynumpix/2.)
  y2 = y1 + ynumpix

  ;; Make sure the endpoints are inside the image.
  IF x1 LT 0 THEN BEGIN
     x2 = x2 + Abs(x1)
     x1 = 0
  ENDIF
  IF y1 LT 0 THEN BEGIN
     y2 = y2 + Abs(y1)
     y1 = 0
  ENDIF
  IF x2 GE (self._xsize-1) THEN BEGIN
     x2 = self._xsize - 1
     x1 = x2 - xnumpix + 1
  ENDIF
  IF y2 GE (self._ysize-1) THEN BEGIN
     y2 = self._ysize - 1
     y1 = y2 - ynumpix + 1
  ENDIF

  ;; Update the zoom coordinate system.
  xvec = Scale_Vector(Findgen(self._xsize), self.init_xr[0], self.init_xr[1])
  xr = [xvec[Round(x1)], xvec[Round(x2)]]
  
  yvec = Scale_Vector(Findgen(self._ysize), self.init_yr[0], self.init_yr[1])
  yr = [yvec[Round(y1)], yvec[Round(y2)]]
  
  self -> SetProperty, ZoomRect=Round([x1, y1, x2, y2])
  self._coords -> SetProperty, XRange=xr, YRange=yr

  ;; Draw it.
  IF ~Keyword_Set(nodraw) THEN self -> Draw

  self -> Report, /Completed

END


;*****************************************************************************************************
;+
; NAME:
;       IGL_Image::ZOOMOUT
;
; PURPOSE:
;
;       This method allows the user to zoom into the image.
;
; SYNTAX:
;
;       self -> ZoomOut, x_img, y_img
;
; ARGUMENTS:
;
;       x_img: The X focal point for zooming. If not provided, the center of the image.
;              The value will be a pixel value in the native image coordinate system.
;              That is to say, a value between 0 and the image X size. The value can
;              be obtained from an event structure by passing the event structure to
;              the method Pixel_To_Value:
;
;                 imgVal = self -> Pixel_To_Value(event.x, event.y, XPixel=x_img, YPixel=y_img)
;                 self -> ZoomIn, x_img, y_img
;
;       y_img: The Y focal point for zooming. If not provided, the center of the image.
;              The value will be a pixel value in the native image coordinate system.
;              That is to say, a value between 0 and the image Y size.
;
; KEYWORDS:
;
;       ALLTHEWAY:  If this keyword is set, the image is zoomed all the way back to its starting value.
;
;       PIXMAP: Set this keyword to a pixmap that can be used to erase the previous
;               contents of the window.
;
;       NODRAW: If this keyword is set, only the coordinates are updated. No drawing occurs.
;
;-
;*****************************************************************************************************
PRO IGL_Image::ZoomOut, x_img, y_img, ALLTHEWAY=alltheway, PIXMAP=pixmap, NODRAW=nodraw 

  @cat_pro_error_handler

  ;; The following code is a slight modification of the CATIMAGE::ZoomOut
  ;; method. It calculates the new data range and updates the
  ;; coordinate system.

  ;; If the data range is static then return immediately
  IF OBJ_ISA_VALID(self._coords,'IGL_Coord') THEN BEGIN
     self._coords -> GetProperty, STATIC=IsStatic
     IF IsStatic THEN RETURN
  ENDIF

  ;; Do we have a focal point?
  IF N_Elements(x_img) EQ 0 THEN BEGIN
     x_img = (self._xsize) / 2
     y_img = (self._ysize) / 2
  ENDIF

  ;; Find the endpoints of a zoom.
  IF Keyword_set(alltheway) THEN BEGIN
     xnumpix = self._xsize
     ynumpix = self._ysize
  ENDIF ELSE BEGIN
     xnumpix = abs(self._x2-self._x1) * 2^0.25
     ynumpix = abs(self._y2-self._y1) * 2^0.25
     IF xnumpix GT self._xsize THEN xnumpix = self._xsize
     IF ynumpix GT self._ysize THEN ynumpix = self._ysize
  ENDELSE
  x1 = x_img - (xnumpix/2.)
  x2 = x1 + xnumpix
  y1 = y_img - (ynumpix/2.)
  y2 = y1 + ynumpix
  
  ;; Make sure the endpoints are inside the image.
  IF x1 LT 0 THEN BEGIN
     x2 = x2 + Abs(x1)
     x1 = 0
  ENDIF
  IF y1 LT 0 THEN BEGIN
     y2 = y2 + Abs(y1)
     y1 = 0
  ENDIF
  IF x2 GE (self._xsize-1) THEN BEGIN
     x2 = self._xsize - 1
     x1 = x2 - xnumpix + 1
  ENDIF
  IF y2 GE (self._ysize-1) THEN BEGIN
     y2 = self._ysize - 1
     y1 = y2 - ynumpix + 1
  ENDIF

  ;; Update the zoom coordinate system.
  xvec = Scale_Vector(Findgen(self._xsize), self.init_xr[0], self.init_xr[1])
  xr = [xvec[Round(x1)], xvec[Round(x2)]]
  
  yvec = Scale_Vector(Findgen(self._ysize), self.init_yr[0], self.init_yr[1])
  yr = [yvec[Round(y1)], yvec[Round(y2)]]
  
  self -> SetProperty, ZoomRect=Round([x1, y1, x2, y2])
  self._coords -> SetProperty, XRange=xr, YRange=yr

  ;; Draw it.
  IF ~Keyword_Set(nodraw) THEN self -> Draw

END


;*****************************************************************************************************
;+
; NAME:
;       IGL_Image::CLEANUP
;
; PURPOSE:
;
;       This is the IGL_Image object class destructor method.
;
; SYNTAX:
;
;       Called automatically when the object is destroyed.
;
;
; ARGUMENTS:
;
;     None.
;
; KEYWORDS:
;
;     None.
;-
;*****************************************************************************************************
PRO IGL_Image::CLEANUP

   @cat_pro_error_handler

   ;; Cleanup the superclass
   self -> SCALEIMAGE::CLEANUP

   ;; Report completion
   self -> Report, /Completed
   
END


;*****************************************************************************************************
;+
; NAME:
;       IGL_Image::INIT
;
; PURPOSE:
;
;       This is the IGL_Image object class creator method.
;
; SYNTAX:
;
;       Called automatically when the object is created.
;
; ARGUMENTS:
;
;     None.
;
; KEYWORDS:
;
;     _EXTRA:   Any keywords accepted by the SCALEIMAGE::Init method.
;
;-
;*****************************************************************************************************
FUNCTION IGL_Image::INIT, image, $
                          INITIAL_XRANGE=initial_xrange, $
                          INITIAL_YRANGE=initial_yrange, $
                          _Extra=extraKeywords


   @cat_func_error_handler

   ;; Call superclass INIT method
   ok = self -> SCALEIMAGE::Init(image,_Extra=extraKeywords)

   IF (NOT ok) THEN RETURN, 0

   ;; Change default name and description for the CAT_IMAGE object
   self -> SetProperty, name='Image', description='Image view'

   ;; Use a 1 to 1 mapping between data range and image pixel
   ;; coordinates if none is provided 
   self -> GetProperty, Xsize=xsize, Ysize=ysize
   IF (n_elements(initial_xrange) EQ 0) THEN initial_xrange = [0,(xsize>1)-1]
   IF (n_elements(initial_yrange) EQ 0) THEN initial_yrange = [0,(ysize>1)-1]

   ;; Load object
   self.init_xr = initial_xrange
   self.init_yr = initial_yrange

   ;; Set up editable properties
   self -> SetPropertyAttribute, 'NAME', sensitive=0
   self -> SetPropertyAttribute, 'DESCRIPTION', sensitive=0
   self -> SetPropertyAttribute, 'POSITION', /HIDE
   self -> SetPropertyAttribute, 'KEEP_ASPECT', /HIDE
   self -> RegisterProperty, 'DELETE', name='Delete object', /boolean

   ;; The color table is handled directly by the window
   self._colors -> RegisterForMessage, self, 'COLORTOOL_TABLECHANGE', /UNREGISTER
   obj_destroy, self._colors

   RETURN, 1

END


;*****************************************************************************************************
;
; NAME:
;       IGL_IMAGE CLASS DEFINITION
;
; PURPOSE:
;
;       This is the IGL_Image object's structure definition code.
;
;*****************************************************************************************************
PRO IGL_Image__DEFINE, class

  COMPILE_OPT idl2, HIDDEN

  class = {IGL_Image, $      
           INHERITS SCALEIMAGE, $   ; This is a SCALEIMAGE object
           init_xr:fltarr(2), $     ; The mapping between data and image pixel X coordinates           
           init_yr:fltarr(2) $      ; The mapping between data and image pixel Y coordinates
          }

  struct_hide, class

END
